package com.evaluation.security;

import java.io.IOException;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
//import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.CharacterEncodingFilter;

import com.evaluation.config.UserSessionListener;
import com.evaluation.service.api.IUserService;

import cn.hutool.core.util.StrUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;

/**
 * SecurityConfig配置
 * 由于spring boot3.0废弃了extends WebSecurityConfigurerAdapter 的方式，所以这里采用添加@Bean新方式
 * @author bob.kuang
 *
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
//public class BootstrapSecurityConfig extends WebSecurityConfigurerAdapter {
public class BootstrapSecurityConfig {
	
	@Value("#{'${studio.excludeuri:''}'.split(',')}")
	private List<String> excludeUri;

	private LdapAuthProvider ldapAuthProvider;
	private IUserService userService;
	///private JwtTokenUtil jwtTokenUtil;

	private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
	
	public BootstrapSecurityConfig(LdapAuthProvider ldapAuthProvider, IUserService userService /**, JwtTokenUtil jwtTokenUtil**/) {
		this.ldapAuthProvider = ldapAuthProvider;
		this.userService = userService;
		///this.jwtTokenUtil = jwtTokenUtil;
	}
	
	@Bean
	public WebSecurityCustomizer webSecurityCustomizer() {
		 return (w->{
			 //w.ignoring().requestMatchers( "/wx/chat" );
			 //忽略后完全不用session，比如invalidSessionStrategy方法就不会调用了
			 
			 w.ignoring().requestMatchers( excludeUri.stream().map(String::trim).map(AntPathRequestMatcher::antMatcher).peek(s -> log.info("Exclude URI [{}]", s.getPattern()))
						.toArray(AntPathRequestMatcher[]::new) );
		 });
	}

	
	//@Override
	//protected void configure(HttpSecurity http) throws Exception {
	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		
		//MvcRequestMatcher.Builder builder = new MvcRequestMatcher.Builder(http.in)
		
		http.authorizeHttpRequests( auth -> auth
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/login" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/logout" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/logoutsuccess" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/images/**" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/js/**" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/jquery/*/**.js" ), 
						AntPathRequestMatcher.antMatcher( "/bootstrap/*/*/**" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/*/*.css" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/*/*.jpg" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/*/*.png" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/css/**" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/fonts/*" ) ).permitAll()
				.requestMatchers( AntPathRequestMatcher.antMatcher( "/*.ico" ) ).permitAll()
				///.requestMatchers(excludeUri.stream().map(String::trim).peek(s -> log.info("Exclude URI [{}]", s)).toArray(String[]::new)).permitAll()
				.requestMatchers(excludeUri.stream().map(String::trim).map(AntPathRequestMatcher::antMatcher).peek(s -> log.info("Exclude URI [{}]", s.getPattern()))
						.toArray(AntPathRequestMatcher[]::new)).permitAll()
//				.requestMatchers( AntPathRequestMatcher.antMatcher( "/wx/chat" ), AntPathRequestMatcher.antMatcher( "/wx/chat/file/**" ) ).anonymous()
				.anyRequest().authenticated()
		);
		
		http.csrf( new Customizer<CsrfConfigurer<HttpSecurity>>() {

			@Override
			public void customize(CsrfConfigurer<HttpSecurity> t) {
				// TODO Auto-generated method stub
				t.disable();
			}
			
		})
		.formLogin( new Customizer<FormLoginConfigurer<HttpSecurity>>() {

			@Override
			public void customize(FormLoginConfigurer<HttpSecurity> t) {
				// TODO Auto-generated method stub
				t.loginPage("/login").failureUrl("/login?error=true").loginProcessingUrl("/logining")
					.permitAll().successForwardUrl("/starter").usernameParameter("username")
					.passwordParameter("password")
					.successHandler( new AuthenticationSuccessHandler() {
						
						@Override
						public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
								Authentication authentication) throws IOException, ServletException {
							HttpSession session = request.getSession();
							
							session.setAttribute("userId", authentication.getPrincipal());

							/**
							List<String> roleList = new ArrayList<>();
							authentication.getAuthorities().forEach( (roles)->{
								String[] rolesList = roles.getAuthority().split(",");
								for (String str : rolesList ) {
									roleList.add( str );
								}
							});
							**/
							
							/**
							Enumeration<String> names = request.getParameterNames();
							while( names.hasMoreElements() ) {
								var name = names.nextElement();
								log.info("{}={}", name, request.getParameter(name));
							}
							
							Enumeration<String> hnames = request.getHeaderNames();
							while( hnames.hasMoreElements() ) {
								var name = hnames.nextElement();
								log.info("{}={}", name, request.getHeader(name));
							}
							
							log.info( request.getParameter("path") );
							**/
							  
							session.setAttribute("roleId", authentication.getAuthorities().stream().map( r -> r.getAuthority().substring(5) ).toList());
							  
							// 登录成功之后将SECURITY放入上下文中
							request.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
							String urlStr = request.getParameter("path");
							if( StrUtil.isBlank( urlStr ) ) {
								urlStr = "/";
							}
							
							
							//String token = jwtTokenUtil.generateToken((String)authentication.getPrincipal(), session.getId(), "pc");
							PwdEncoder md5 = new Md5PwdEncoder();
							var token = md5.encodePassword( session.getId(), (String)authentication.getPrincipal());
							session.setAttribute("token", token);
							
							redirectStrategy.sendRedirect(request, response, urlStr);

							//request.getRequestDispatcher("/starter").forward(request, response);
							
							userService.updateUserLoginTime( authentication.getPrincipal(), getIpAddr(request) );
			
							UserSessionListener.setOnlineUser( authentication.getPrincipal().toString() );
						}
					});
				
			}
		} )
		.sessionManagement( new Customizer<SessionManagementConfigurer<HttpSecurity>>() {

			@Override
			public void customize(SessionManagementConfigurer<HttpSecurity> t) {
				
				t.invalidSessionStrategy( new CustomerInvalidSessionStrategy( "/login" ) )
					/**.invalidSessionUrl("/login")**/
					.maximumSessions(1).sessionRegistry( sessionRegistry() );
			}
		})
		.logout( new Customizer<LogoutConfigurer<HttpSecurity>>() {

			@Override
			public void customize(LogoutConfigurer<HttpSecurity> t) {
				// TODO Auto-generated method stub
				t.logoutUrl("/logout").logoutSuccessHandler( new LogoutSuccessHandler() {
					@Override
					public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
						throws IOException, ServletException {
						// TODO Auto-generated method stub
						///System.out.println( authentication );
						 
						redirectStrategy.sendRedirect( request, response, "/login");
					}
	        	} )
				.deleteCookies("JESSIONID");
			}
			
		});
		
		/**
		http.csrf().disable()
        	.formLogin()
        	.loginPage("/login").failureUrl("/login?error=true")
        	.loginProcessingUrl("/logining").permitAll().successForwardUrl("/starter")
        	.usernameParameter("username").passwordParameter("password")
        	.successHandler( new AuthenticationSuccessHandler( ) {
				  
				  @Override
				  public void onAuthenticationSuccess(HttpServletRequest request,
						  HttpServletResponse response, Authentication authentication) throws IOException, ServletException { // TODO Auto-generated method stub
					  
					  //System.out.println("登陆成功"+authentication);
				  
					  request.getSession().setAttribute("userId", authentication.getPrincipal());
					  ///request.getSession().setAttribute("roleId", authentication.getAuthorities());
					  
					  List<String> roleList = new ArrayList<>();
					  authentication.getAuthorities().forEach( (roles)->{
						  String[] rolesList = roles.getAuthority().split(",");
						  for (String str : rolesList ) {
							  roleList.add( str );
						  }
					  });
					  
					  request.getSession().setAttribute("roleId", roleList);
				  
					  // 登录成功之后将SECURITY放入上下文中
					  request.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
					  response.sendRedirect("/");
					  //request.getRequestDispatcher("/starter").forward(request, response);
					
					  userService.updateUserLoginTime( authentication.getPrincipal(), getIpAddr(request) );
	
					  UserSessionListener.setOnlineUser( authentication.getPrincipal().toString() );
				  }
        	})
        	.and()
        	.sessionManagement().invalidSessionUrl("/login")//.maximumSessions(2)
        	.and()
        	//.authorizeHttpRequests()
        	//.and()
        	.logout().logoutUrl("/logout").logoutSuccessHandler(new LogoutSuccessHandler() {
				@Override
				public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
					throws IOException, ServletException {
					// TODO Auto-generated method stub
					///System.out.println( authentication );
				
					response.sendRedirect("/login");
				}
        	}).deleteCookies("JESSIONID")
		/*
		 * .and() .httpBasic()
		 */;

		/////http.headers().frameOptions().sameOrigin(); 
		
		http.headers( new Customizer<HeadersConfigurer<HttpSecurity>>() {

			@Override
			public void customize(HeadersConfigurer<HttpSecurity> t) {
				//sameOrigin 同源可以被iframe嵌入 20180723
				
				t.frameOptions( new Customizer<HeadersConfigurer<HttpSecurity>.FrameOptionsConfig>() {

					@Override
					public void customize(HeadersConfigurer<HttpSecurity>.FrameOptionsConfig t) {
						// TODO Auto-generated method stub
						t.sameOrigin();
					}
				});
				
			}
			
		});
		
		//解决中文乱码问题 
        CharacterEncodingFilter filter = new CharacterEncodingFilter(); 
        filter.setEncoding("UTF-8"); filter.setForceEncoding(true); 
        //
        http.addFilterBefore(filter,CsrfFilter.class); 
        
        return http.build();
	}

	/**
	@Override
	protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
		/// authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService(userDetailsService());

		authManagerBuilder.authenticationProvider(ladpAuthProvider);
		

	} **/
	
	/**
     * 认证管理器，登录的时候参数会传给 authenticationManager
     *
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
    	authManagerBuilder.authenticationProvider(ldapAuthProvider);
    	
        //return authenticationConfiguration.getAuthenticationManager();
    	
    	return authManagerBuilder.getOrBuild();
    }

	
	/** 用于获得ip的header name */
	//private static final String[] headers = { "x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP" };
	private static String getIpAddr(HttpServletRequest request) {
		String ip;
		for (String header : new String[]{ "x-forwarded-for", "Proxy-Client-IP", "WL-Proxy-Client-IP" }) {
			ip = request.getHeader(header);
			if (!StringUtils.hasText(ip) && !"unknown".equalsIgnoreCase(ip)) {
				if(ip.contains(", ")){
					return ip.split(", ")[0].trim();
				}else{
					return ip;
				}
			}
		}
		if(request.getRemoteAddr() != null && request.getRemoteAddr().contains(", "))
			return request.getRemoteAddr().split(", ")[0];
		else
		return request.getRemoteAddr();
    }

	@Bean
	public SessionRegistry sessionRegistry() {
		return new SessionRegistryImpl();
	}
	
	@Bean
	public HttpSessionEventPublisher httpSessionEventPublisher() {
		return new HttpSessionEventPublisher();
	}
}
